package com.wut.minio.utils;

import cn.hutool.core.util.StrUtil;
import com.wut.core.config.exception.GlobalException;
import com.wut.minio.config.MinioConfig;
import io.minio.BucketExistsArgs;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.ListObjectsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveBucketArgs;
import io.minio.RemoveObjectArgs;
import io.minio.RemoveObjectsArgs;
import io.minio.Result;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@Component
@RequiredArgsConstructor
public class MinioUtil {

    private final MinioConfig minioConfig;
    private final MinioClient minioClient;

    /**
     * 初始化 minio 配置
     */
    @PostConstruct
    private void init() {
        try {
            if (StrUtil.isNotEmpty(minioConfig.getBucketName()) && !bucketExist(minioConfig.getBucketName())) {
                createBucket(minioConfig.getBucketName());
            }
        } catch (Exception e) {
            log.error("初始化minio配置异常: {}", e.getMessage());
        }
    }

    /**
     * 检查存储 Bucket 是否存在
     *
     * @param bucketName Bucket存储桶名称
     * @return Boolean
     */
    public Boolean bucketExist(String bucketName) {
        try {
            return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
    }

    /**
     * 创建存储 Bucket
     *
     * @param bucketName Bucket存储桶名称
     * @return Boolean
     */
    public Boolean createBucket(String bucketName) {
        checkBucketNoExist(bucketName);
        try {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
        return true;
    }

    /**
     * 删除存储 Bucket
     *
     * @param bucketName Bucket存储桶名称
     * @return Boolean
     */
    public Boolean removeBucket(String bucketName) {
        //TODO 存储桶为空时才能删除成功
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
        return true;
    }

    /**
     * 获取全部bucket
     *
     * @return 存储桶列表
     */
    public List<Bucket> getBuckets() {
        try {
            return minioClient.listBuckets();
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
    }

    /**
     * 文件上传
     *
     * @param bucketName Bucket存储桶名称
     * @param fileName   文件名
     * @param file       文件
     * @return 文件URL
     */
    public String upload(String bucketName, String fileName, MultipartFile file) {
        bucketName = StrUtil.blankToDefault(bucketName.trim(), minioConfig.getBucketName());
        checkBucketExist(bucketName);
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(fileName)
                .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
            // 文件名称相同会覆盖
            minioClient.putObject(objectArgs);

            return getFilePath(bucketName, fileName);
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
    }

    /**
     * 获取文件路径
     *
     * @param bucketName Bucket存储桶名称
     * @param fileName   文件名
     * @return 文件URL
     */
    public String getFilePath(String bucketName, String fileName) {
        bucketName = StrUtil.blankToDefault(bucketName.trim(), minioConfig.getBucketName());
        checkBucketExist(bucketName);
        // 查看文件地址
        GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs.builder()
            .bucket(bucketName)
            .object(fileName)
            .method(Method.GET)
            .build();
        try {
            return minioClient.getPresignedObjectUrl(build);
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
    }

    /**
     * 获取文件路径
     *
     * @param bucketName Bucket存储桶名称
     * @param fileName   文件名
     * @param expires    过期时长(秒)
     * @return 文件URL
     */
    public String getFilePath(String bucketName, String fileName, Integer expires) {
        bucketName = StrUtil.blankToDefault(bucketName.trim(), minioConfig.getBucketName());
        checkBucketExist(bucketName);
        if (expires < 1) {
            throw new GlobalException("过期时长小于1s");
        }
        // 查看文件地址
        GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs.builder()
            .bucket(bucketName)
            .object(fileName)
            .method(Method.GET)
            .expiry(expires)
            .build();
        try {
            return minioClient.getPresignedObjectUrl(build);
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
    }

    /**
     * 文件下载
     *
     * @param bucketName Bucket存储桶名称
     * @param fileName   文件名
     * @param response   HttpServletResponse
     * @return Boolean
     */
    public Boolean download(String bucketName, String fileName, HttpServletResponse response) {
        bucketName = StrUtil.blankToDefault(bucketName.trim(), minioConfig.getBucketName());
        checkBucketExist(bucketName);
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(fileName).build();
        try (GetObjectResponse getObjectResponse = minioClient.getObject(objectArgs)) {
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
                while ((len = getObjectResponse.read(buf)) != -1) {
                    os.write(buf, 0, len);
                }
                os.flush();
                byte[] bytes = os.toByteArray();
                response.setCharacterEncoding("utf-8");
                // 设置强制下载不打开
                //res.setContentType("application/force-download");
                response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
                try (ServletOutputStream stream = response.getOutputStream()) {
                    stream.write(bytes);
                    stream.flush();
                }
            }
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
        return true;
    }

    /**
     * 文件下载(断点续传)(未完成)
     *
     * @param request    HttpServletRequest
     * @param bucketName Bucket存储桶名称
     * @param fileName   文件名
     * @param response   HttpServletResponse
     * @return Boolean
     */
    public Boolean downloadByBreakpoint(HttpServletRequest request, String bucketName, String fileName, HttpServletResponse response) {
        bucketName = StrUtil.blankToDefault(bucketName.trim(), minioConfig.getBucketName());
        checkBucketExist(bucketName);
        try {
            StatObjectResponse statObject = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
            long fileSize = statObject.size();
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(fileName).build();

        return true;
    }

    /**
     * 查询某个存储桶中的所有文件
     *
     * @param bucketName Bucket存储桶名称
     * @return 文件列表
     */
    public List<Item> listObjects(String bucketName) {
        checkBucketExist(bucketName);
        Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
        List<Item> items = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                items.add(result.get());
            }
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
        return items;
    }

    /**
     * 删除文件
     *
     * @param bucketName Bucket存储桶名称
     * @param fileName   文件名
     * @return Boolean
     */
    public Boolean remove(String bucketName, String fileName) {
        bucketName = StrUtil.blankToDefault(bucketName.trim(), minioConfig.getBucketName());
        bucketExist(bucketName);
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
        return true;
    }

    /**
     * 批量删除文件
     *
     * @param bucketName Bucket存储桶名称
     * @param fileNames  文件名集合
     * @return Boolean
     */
    public Boolean remove(String bucketName, List<String> fileNames) {
        bucketName = StrUtil.blankToDefault(bucketName.trim(), minioConfig.getBucketName());
        checkBucketExist(bucketName);
        List<DeleteObject> deleteObjects = fileNames.stream().map(DeleteObject::new).collect(Collectors.toList());
        try {
            Iterable<Result<DeleteError>> deleteErrorResults = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(deleteObjects).build());
            for (Result<DeleteError> deleteErrorResult : deleteErrorResults) {
                DeleteError deleteError = deleteErrorResult.get();
                throw new GlobalException("删除文件{}失败,{}", deleteError.objectName(), deleteError.message());
            }
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
        return true;
    }

    /**
     * 检查存储 Bucket 是否存在
     *
     * @param bucketName Bucket存储桶名称
     */
    private void checkBucketExist(String bucketName) {
        boolean flag = bucketExist(bucketName);
        if (!flag) {
            throw new GlobalException("{}不存在", bucketName);
        }
    }

    /**
     * 检查存储 Bucket 是否不存在
     *
     * @param bucketName Bucket存储桶名称
     */
    private void checkBucketNoExist(String bucketName) {
        boolean flag = bucketExist(bucketName);
        if (flag) {
            throw new GlobalException("{}已存在", bucketName);
        }
    }
}
